% El predicado longitudFilas verifica que el tamaño todas las filas de la matriz
% pasada como parámetro tienen longitud C.

    longitudFilas([H|[]], C) :- length(H, C).
    longitudFilas([H|T], C) :- length(H, C), longitudFilas(T, C).

% El predicado contenido verifica que todas las posiciones de la matriz contengan
% el caracter C.

    contenidoFila([H|[]], H).
    contenidoFila([H|T], H) :- contenidoFila(T, H).
    contenido([H|[]], C) :- contenidoFila(H, C).
    contenido([H|T], C) :- contenidoFila(H, C), contenido(T, C).

% El tablero inicial estará representado por listas que contendrán los elementos
% de las filas.

    tableroInicial(F, C, T) :- 
        length(T, F), 
        longitudFilas(T, C), 
        contenido(T, 'a').

% El predicado hayBarco indica que en la celda (NF, NC) se encuentra ubicado un barco.
% hayBarco: NF -> Numero de fila.
%           NC -> Numero de columna.

    hayBarco(NF, NC, X) :- 
        barco(T, 'v', F, C), 
        NC = C, NF >= F, 
        NF < F + T, 
        X = (T, 'v', F, C).

    hayBarco(NF, NC, X) :- 
        barco(T, 'h', F, C),
        NF = F,
        NC >= C, 
        NC < C + T, 
        X = (T, 'h', F, C).

% diferenciaFila: [H0|T0] -> Primera lista.
%                 [H1|T1] -> Segunda lista.
%                 N0 -> Mensaje desde el hijo: 0 si se encontró una posición igual en ambas matrices,
%                                              1 en caso contrario.
%                 N1 -> Mensaje desde el padre: 0 si ya se encontró una posición igual en ambas matrices,
%                                               1 en caso contrario.
%                 NF -> Numero de la fila a analizar
%                 NC -> Numero de la columna a analizar
%                 Barco -> Si la diferencia encontrada es un golpe a un barco, aqui esta contenido el mismo.

    diferenciaFila(['a'|[]], ['g'|[]], 0, 1, NF, NC, Barco) :- hayBarco(NF, NC, Barco).

    diferenciaFila(['a'|[]], ['f'|[]], 0, 1, NF, NC, _) :- 
        \+fallo(NF, NC),
        \+hayBarco(NF, NC, _),
        assert(fallo(NF, NC)).

    diferenciaFila([X|[]], [X], 1, _, _, _, _).

    diferenciaFila(['a'|T0], ['g'|T1], 0, 1, NF, NC, Barco) :- 
        hayBarco(NF, NC, Barco),
        NC1 is NC + 1, 
        diferenciaFila(T0, T1, 1, 0, NF, NC1, Barco).

    diferenciaFila(['a'|T0], ['f'|T1], 0, 1, NF, NC, Barco) :- 
        \+fallo(NF, NC),
        \+hayBarco(NF, NC, Barco),
        assert(fallo(NF, NC)),
        NC1 is NC + 1,  
        diferenciaFila(T0, T1, 1, 0, NF, NC1, Barco).

    diferenciaFila([H0|T0], [H0|T1], N0, N1, NF, NC, Barco) :- 
        NC1 is NC + 1, 
        diferenciaFila(T0, T1, N0, N1, NF, NC1, Barco).

% El predicado diferencia indica que los tableros dados difieren en
% en 1 posicion.
% diferencia: [H0|T0] -> Primera lista.
%             [H1|T1] -> Segunda lista.
%             N -> 1 Si se desea encontrar una diferencia, 0 en caso contrario.
%             NF -> Numero de la fila a analizar.
%             Barco -> Si la diferencia es un golpe a un barco, aqui esta contenido el mismo

    diferencia([H0|[]], [H1|[]], 1, NF, Barco) :- diferenciaFila(H0, H1, 0, 1, NF, 0, Barco).

    diferencia([H0|[]], [H1|[]], 0, NF, Barco) :- diferenciaFila(H0, H1, 1, 0, NF, 0, Barco).

    diferencia([H0|T0], [H1|T1], 1, NF, Barco) :- 
        diferenciaFila(H0, H1, N0, 1, NF, 0, Barco), 
        NF1 is NF + 1, 
        diferencia(T0, T1, N0, NF1, Barco).

    diferencia([H0|T0], [H1|T1], 0, NF, Barco) :- 
        diferenciaFila(H0, H1, _, 0, NF, 0, Barco),
        NF1 is NF + 1, 
        diferencia(T0, T1, 0, NF1, Barco).

% Predicado que indica si T1 corresponde a un ataque en T0.

    ataque(T0, T2, F, C) :- 
        length(T0, F), 
        longitudFilas(T0, C), 
        length(T1, F), 
        longitudFilas(T1, C), 
        diferencia(T0, T1, 1, 0, Barco),
        actualizarBarco(T1, T2, Barco).


% Caso base: se está en la columna apropiada, se descarta el contenido anterior y se sustituye
% por la letra apropiada

    sustituirLetraC(C, [_|T], C, L, [L|T]).

% Caso recursivo: aún no se ha llegado a la letra y se sigue la llamada recursiva

    sustituirLetraC(C, [H|T0], Ca, L, F) :-
        Cn is Ca + 1,
        sustituirLetraC(C, T0, Cn, L, T1),
        F = [H|T1].

    sustituirLetra(_, _, _, T, T, _, 0).

% Caso base: se encontró la fila que contiene la letra a modificar y se llama la sustitución de letra

    sustituirLetra(F, C, L, [H0|T], Tablero, F, 1) :-
        sustituirLetraC(C, H0, 0, L, H1),
        Tablero = [H1|T].

% Caso recursivo: se busca la fila que contiene la letra a modificar

    sustituirLetra(F, C, L, [H|T0], Tablero, Fa, 1) :-
        Fn is Fa + 1,
        sustituirLetra(F, C, L, T0, T1, Fn, 1),
        Tablero = [H|T1].


% Caso base: Todas las letras del barco son una 'g'.
% Se devuelve el tablero en T para indicar que hay que hacer el cambio
verificarBarco(0, _, _, _, T, T, 1).

% Caso base: ocurre cuando el barco no esta listo para ser hundido (pudo ya haber sido hundido)
verificarBarco(L, _, Y, X, T, T, 0) :-
    L \= 0,
    \+verificarLetra(Y, X, 'g', T, 0).

% Caso recursivo: se verifica la letra. También se hace la verificación recursiva
verificarBarco(L0, 'h', Y, X0, T0, T2, V) :-
    verificarLetra(Y, X0, 'g', T0, 0),
    L1 is L0 - 1,
    X1 is X0 + 1,
    verificarBarco(L1, 'h', Y, X1, T0, T1, V),
    sustituirLetra(Y, X0, 'h', T1, T2, 0, V).

% Caso recursivo: se verifica la letra. También se hace la verificación recursiva
verificarBarco(L0, 'v', Y0, X, T0, T2, V) :-
    verificarLetra(Y0, X, 'g', T0, 0),
    L1 is L0 - 1,
    Y1 is Y0 + 1,
    verificarBarco(L1, 'v', Y1, X, T0, T1, V),
    sustituirLetra(Y0, X, 'h', T1, T2, 0, V).

% Actualizar barco no hace nada cuando no se le pasa un barco a revisar si fue hundido
actualizarBarco(T, T, B) :- var(B).

% Dado un barco instanciado, se revisa si fue hundido en esta jugada
actualizarBarco(T0, T1, B) :- 
    nonvar(B),
    arg(1, B, L),
    arg(2, B, H1),
    arg(1, H1, D),
    arg(2, H1, H2),
    arg(1, H2, Y),
    arg(2, H2, X),
    verificarBarco(L, D, Y, X, T0, T1, _).


leerBarcos(0).
leerBarcos(B0) :- 
         write('Informacion de Barco:\n'),
         write('Tamano: '), read(T),
         write('Direccion: '), read(D),
         write('Fila Inicial: '), read(F),
         write('Columna Inicial: '), read(C),
         assert(barco(T, D, F, C)),
         B1 is B0 - 1, leerBarcos(B1).

ejecutarJugada(T0, _, _, _) :-
         estadoFinal(T0).

ejecutarJugada(T0, F, C, P0) :-
         \+checked(T0, P0),
         P0 >= 1,
         P1 is P0 - 1,
         ataque(T0, T1, F, C),
         mostrarTablero(T1),
         assert(checked(T0, P0)),
         ejecutarJugada(T1, F, C, P1).
         

jugar :- 
        abolish(barco/4),
        abolish(fallo/2),
        abolish(checked/2),
        dynamic(barco/4),
        dynamic(fallo/2),
        dynamic(checked/2),
        write('Num. de Filas: '), read(F),
        write('Num. de Columnas: '), read(C),
        write('Cant. de Barcos: '), read(B),
        leerBarcos(B),
        write('Cant. de proyectiles disponibles: '), read(P),
        tableroInicial(F, C, T),
        ejecutarJugada(T, F, C, P).

jugar :- write('No hay suficientes proyectiles').

% Predicado que determina si la letra ubicada en la columna C de la fila T es una 'h'
% verificarLetraC: C -> Columna que se desea verificar
%                  H|T -> Lista que contiene toda la fila
%                  Ca -> Columna que se está verificando
%                  L -> Letra que se está verificando

    verificarLetraC(C, [L|_], C, L).

    verificarLetraC(C, [_|T], Ca, L) :- 
        C =\= Ca, 
        Cn is Ca + 1, 
        verificarLetraC(C, T, Cn, L).

% Predicado que determina ubica la fila que se desea acceder para verificar que una entrada es 'h'
% verificarLetra: F -> Fila a la que se desea acceder
%                 C -> Columna a la que se desea acceder
%                 H|T -> Tablero que se desea analizar
%                 Fa -> Fila actual del tablero

    verificarLetra(F, C, L, [H|_], F) :- verificarLetraC(C, H, 0, L).

    verificarLetra(F, C, L, [_|T], Fa) :- 
        F =\= Fa, 
        Fn is Fa + 1, 
        verificarLetra(F, C, L, T, Fn).

% Predicado que determina si todos los elementos de la lista corresponden
% barcos hundidos en el tablero T
% verificarEstado: H|Ta -> Lista que contiene los barcos que se desean verificar
%                  T -> Tablero sobre el que se desean verificar los barcos.

    verificarEstado([], _).

    verificarEstado([H|Ta], T) :- 
        arg(2, H, F0), 
        arg(2, F0, F1), 
        arg(1, F1, F), 
        arg(2, F1, C),
        verificarLetra(F, C, 'h', T, 0), verificarEstado(Ta, T).

% Predicado que determina si en el tablero T se han hundido todos los barcos

    estadoFinal(T0) :- findall((T, D, F, C), barco(T, D, F, C), L), verificarEstado(L, T0).

% Predicado que permite imprimir una fila del tablero
% mostrarFila: F -> Fila que se desea mostrar.

    mostrarFila([]).
    mostrarFila([H|T]) :- write(H), tab(1), mostrarFila(T).

% Predicado que permite mostrar por pantalla el tablero T.

    mostrarTablero([]) :- nl.
    mostrarTablero([H|T]) :- mostrarFila(H), nl, mostrarTablero(T).